#include "ProcUtil.h"
#include "SocketComm.h"
#include "Frame.h"
#include <vector>
#include <stdarg.h>
#include <bitset>
#include <errno.h>

ProcUtil::ProcUtil()
{
    waitForAck = false;
    sockets.clear();
    procsDone.set();
}

ProcUtil::ProcUtil(int processAddr)
{
    procAddr = processAddr;
}

//this function will route the received Frame to the appropriate
//node used, using the correct socket. if the destination node
//is connected directly to the node that received the Frame, return
//that SocketComm object, else return the SocketComm object that 
//connects to the Parent node.
SocketComm* ProcUtil::route(Frame frame)
{
    SocketComm *toParent, *toUse = 0;
    int frameDest = frame.getDestinationer();
    bool canSendDirect = false;
    
    //if frame destination is not for the current receiving node
    //determine which node to route it to.
    if(procAddr != frameDest)
    {
        //cycle through the set of available sockets for this node
        for(unsigned int i = 0; i < sockets.size(); i++)
        {
            //if frame is destined for a node that the current node
            //is directly connected to, send through that node, and return
            //the appropriate SocketComm object, else, send through the parent
            if(
                (sockets[i].getPair())[0] == frameDest
                || (sockets[i].getPair())[1] == frameDest
              )
            {
                toUse = &(sockets[i]);
                canSendDirect = true;
            }
            if((sockets[i].getPair())[0] == 0)
            {
                toParent = &(sockets[i]);
            }
        }
        if(!canSendDirect)
        {
            toUse = toParent;
        }
    }

    return toUse;
}

//receives a Frame and determines the Frame's type, and how to handle it
Frame ProcUtil::handleFrame(Frame toHandle)
{    
    //if Frame isn't ment for the receiving node, send it on its way to the
    //appropriate destination
    if(toHandle.getDestinationer() != procAddr)
    {
        send(toHandle);

        toHandle = Frame::getNullFrame();
    }
    //if ACK frame is received change waitForAck to False, so the node
    //knows that it can now create and send its next message. 
    //if it is a NACK frame, resend the last frame
    else if(toHandle.getFrameType() == Frame::ACK_FRAME)
    {
        if(atoi((toHandle.getMessage())) == Frame::NACK)
        {
            waitForAck = true;
            send(lastSent);
        }
        else if(atoi((toHandle.getMessage())) == Frame::ACK)
        {
            waitForAck = false;
        }
    }
    //if data frame is received and is destined for the receiving node, immediately
    //send out an ACK frame to the appropriate node
    else if(toHandle.getFrameType() == Frame::DATA_FRAME)
    {
        char msg[10];
        sprintf(msg, "%d", Frame::ACK);
        Frame sendAck = Frame(toHandle.getDestinationer(), toHandle.getSender(), false, msg);
        
        send(sendAck);
    }

    return toHandle;
}

bool ProcUtil::waitingForAck()
{
    return waitForAck;
}

void ProcUtil::addSocket(SocketComm socket)
{
    sockets.push_back(socket);
}


Frame ProcUtil::listen()
{
    Frame receive[1];
    int numBytes = 0, endToUse;
    
    //create a NULL frame
    receive[0] = Frame::getNullFrame();

    //cycle through the available sockets and read from them
    for(unsigned int i = 0; i < sockets.size() && numBytes == 0; i++)
    {
        endToUse = getSocketEnd(sockets[i].getPair());
        numBytes = read((sockets[i].getSocket())[endToUse], receive, sizeof(Frame));

        //if no bytes were read and an error occurred (EAGAIN is typically received if the
        //pipe is empty) reset numBytes to zero
        if(numBytes < 0 && errno == EAGAIN)
        {
            numBytes = 0;
        }
    }

    //if a frame was received, determine the appropriate course of action
    if(numBytes > 0)
    {
        receive[0] = handleFrame(receive[0]);
    }

    return receive[0];
}

void ProcUtil::send(Frame toSend)
{
    Frame wrap[1];
    SocketComm *sockToUse;
    int endToUse;

    wrap[0] = toSend;

    //if frame that is to send is a data frame and coming from the current node
    //set waitForAck to true so node doesn't send anymore frames until it has received
    //an ACK from the destination node
    if(toSend.getSender() == procAddr && toSend.getFrameType() == Frame::DATA_FRAME)
    {
        waitForAck = true;
        lastSent = toSend;
    }

    sockToUse = route(toSend);
    endToUse = getSocketEnd(sockToUse->getPair());

    //write frame to the socket destined for the proper node
    write((sockToUse->getSocket())[endToUse], wrap, sizeof(Frame));
}

//get appropriate end of the pipe to use for socket writing and reading
int ProcUtil::getSocketEnd(int pair[])
{
    if(pair[1] == procAddr)
    {
        return SocketComm::node2End;
    }
    else
    {
        return SocketComm::node1End;
    }
}

//returns the address of the current node (e.g. 0, 1, 2, 3, 4)
int ProcUtil::getAddress()
{
    return procAddr;
}

//checks to see if all other processes are done. if so, then
//the current node can notify the parent node, and then exit
bool ProcUtil::isDone()
{
    for(int i = 0; i < numProcs; i++)
    {
        if(procsDone[i] == false)
        {
            return false;
        }
    }

    return true;
}

//Checks the given element in the procsDone bit array 
//to find out if it is done
bool ProcUtil::isDone(int check)
{
    return procsDone[check];
}

//sets the current node to finsihed 
void ProcUtil::setDone(int finished)
{
    procsDone[finished] = true;
}
